#include "street.h"

#include <vector>
#include <fstream>
#include <iostream>

typedef struct {
	std::vector<glm::vec3> vertices;
	std::vector<glm::vec3> normals;
	std::vector<unsigned short> indices;
} mesh_t;

typedef struct {
	glm::vec3 center;
	glm::vec3 extent;
} box_t;

typedef struct {
	glm::vec3 pt;
	glm::vec3 normal;
} vertex_t;

typedef struct {
	glm::vec3 center;
	glm::vec3 normal;
	glm::vec3 up;
	glm::vec2 extents;
} rectangle_t;

typedef struct {
    vertex_t upper_left;
    vertex_t lower_left;
    vertex_t lower_right;
    vertex_t upper_right;
} quad_t;

unsigned short appendPointForIndex(mesh_t * mesh, const vertex_t & vertex) {
    mesh->normals.push_back(vertex.normal);
    mesh->vertices.push_back(vertex.pt);
    return (unsigned short) mesh->vertices.size() - 1;
}

void appendQuad(mesh_t * mesh, const quad_t & quad) {
    unsigned short ind_ul = appendPointForIndex(mesh, quad.upper_left);
    unsigned short ind_ll = appendPointForIndex(mesh, quad.lower_left);
    unsigned short ind_lr = appendPointForIndex(mesh, quad.lower_right);
    unsigned short ind_ur = appendPointForIndex(mesh, quad.upper_right);
	unsigned short added [] = { ind_ul, ind_ll, ind_lr,
		ind_ul, ind_lr, ind_ur};
	for (int i = 0; i < 6; ++i) {
        mesh->indices.push_back(added[i]);
	}
}

void appendRectangle(mesh_t * mesh, const rectangle_t & plane) {
    vec3 normal = plane.normal;
    vec3 up = plane.up;
    vec3 left = cross(up,normal);
    float half_width = plane.extents.x/2.0f;
    float half_height = plane.extents.y/2.0f;
	vertex_t upper_left = {plane.center + half_width*left + half_height*up, normal};
	vertex_t lower_left = {plane.center + half_width*left - half_height*up, normal};
	vertex_t lower_right = {plane.center - half_width*left - half_height*up, normal};
	vertex_t upper_right = {plane.center - half_width*left + half_height*up, normal};
	quad_t quad = {upper_left, lower_left, lower_right, upper_right};
    appendQuad(mesh, quad);
}


void appendSide(mesh_t * mesh, const box_t & box, int side, bool positive) {
	int mult = positive ? 1 : -1;
    vec3 normal; //zeros
    normal[side] = 1.0f;
    normal *= mult;
	int other1 = (side + 1) % 3;
    int other2 = (side + 2) % 3;
    vec3 side_center = box.center + (box.extent[side]/2.0f)*normal;
    vec2 extents(box.extent[other1], box.extent[other2]);
    vec3 up;
    up[other2] = 1.0f;
	rectangle_t plane = {side_center, normal, up, extents};
    appendRectangle(mesh,plane);
}

void appendBox(mesh_t * mesh, const box_t & box) {
	for (int i = 0; i < 3; i ++) {
		for (int j = 0; j < 2; j++) { 
			appendSide(mesh, box, i,j ? true : false); 
		}
	}
}

box_t boxes [] = { 
//	{vec3(0,0,0),vec3(100,100,100)}, //Center, extents
	{vec3(0,0,0),vec3(10,10,10)},
	{vec3(20,0,0),vec3(10,10,10)},
	{vec3(0,20,0),vec3(10,10,10)},
	{vec3(0,0,20),vec3(10,10,10)}
};

int num_boxes = 3;
const int DUMP_SIZE = 1024;

fs_device_mesh uploadMesh(const mesh_t & mesh, vec3 color) {
	fs_device_mesh out;
	out.num_indices = 0;
	fs_device_mesh_initToZero(out);
	//Allocate vertex array
	//Vertex arrays encapsulate a set of generic vertex attributes and the buffers they are bound too
	//Different vertex array per mesh.
	glGenVertexArrays(1, &(out.vertex_array));
    glBindVertexArray(out.vertex_array);
    
	//Allocate vbos for data
	glGenBuffers(1,&(out.vbo_vertices));
	glGenBuffers(1,&(out.vbo_normals));
	glGenBuffers(1,&(out.vbo_indices));
	glGenBuffers(1,&(out.vbo_colors));
    
	//Upload vertex data
	glBindBuffer(GL_ARRAY_BUFFER, out.vbo_vertices);
	glBufferData(GL_ARRAY_BUFFER, mesh.vertices.size()*sizeof(vec3), &mesh.vertices[0], GL_STATIC_DRAW);
	glVertexAttribPointer(fs_vertex_attribute::POSITION, 3, GL_FLOAT, GL_FALSE,0,0);
	glEnableVertexAttribArray(fs_vertex_attribute::POSITION);

    //VBO for normal data
	glBindBuffer(GL_ARRAY_BUFFER, out.vbo_normals);
	glBufferData(GL_ARRAY_BUFFER, mesh.normals.size()*sizeof(vec3), &mesh.normals[0], GL_STATIC_DRAW);
	glVertexAttribPointer(fs_vertex_attribute::NORMAL, 3, GL_FLOAT, GL_FALSE,0,0);
	glEnableVertexAttribArray(fs_vertex_attribute::NORMAL);

	//VBO for color data
	float *colorA = new float[3*mesh.vertices.size()];
	for(unsigned int i = 0; i < mesh.vertices.size(); ++i) {
		colorA[3*i] = color.r;
		colorA[3*i + 1] = color.g;
		colorA[3*i + 2] = color.b;
	}
	glBindBuffer(GL_ARRAY_BUFFER, out.vbo_colors);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertices.size(), colorA, GL_STATIC_DRAW);
	glVertexAttribPointer(fs_vertex_attribute::COLOR, 3, GL_FLOAT, GL_FALSE,0,0);
	glEnableVertexAttribArray(fs_vertex_attribute::COLOR);
	delete colorA;

     //indices
	// We need to flip all the windings around
	GLushort *rIndex = new GLushort[mesh.indices.size()];
	for(unsigned int i = 0; i < mesh.indices.size(); ++i)
		rIndex[i] = mesh.indices[mesh.indices.size() - i - 1];
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out.vbo_indices);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices.size()*sizeof(GLushort), rIndex, GL_STATIC_DRAW);
    out.num_indices = mesh.indices.size();
	delete rIndex;
	//Unplug Vertex Array
    glBindVertexArray(0);

    return out;
}

fs_Object* createStreet(vec3 color) {
	mesh_t street;
	char dump[DUMP_SIZE];
    ifstream in(CITY_PATH);

	if(!in.good()) {
		cout << "Unable to open: " << CITY_PATH << "\n";
		return NULL;
	}

	while (in.good()) {
		string tok;
		in >> tok;
		if (tok == "**" ) { //Comment
			in.getline(dump, DUMP_SIZE -1 );
			continue;
		}
		else if (tok == "center:") { //box
			float cx, cy ,cz;
			in >> cx >> cy >> cz;
			in >> tok;
			if (tok == "extents:") {
				float ex, ey, ez;
				in >> ex >> ey >> ez;
				box_t box = {vec3(cx,cy,cz),vec3(ex,ey,ez)};
				appendBox(&street, box);
			}
			else {
				cout << "Error parsing city.txt\n";
				in.close();
				return NULL;
			}
		}
		else {
			cout << "Error parsing city.txt\n";
			in.close();
			return NULL;
		}
	}
	in.close();


	fs_device_mesh device_street = uploadMesh(street, color);
	cout << "Successfully created street.\n";
	return new fs_Object(device_street);
}